using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows;
using FluentSilverlight.Commands;
using FluentSilverlight.Expressions;
using FluentSilverlight.ValueConverters;

namespace FluentSilverlight.Binders
{
    public abstract class FrameworkElementBinder<THIS, TControl, TModel>
        where TControl : FrameworkElement
        where TModel : class
    {
        protected readonly TModel model;
        protected readonly TControl control;
        protected PropertyBinder<TModel> propertyBinder;
        private Expression<Func<TModel, object>> visibilityExpression;

        protected FrameworkElementBinder(TModel model, TControl control)
        {
            this.model = model;
            this.control = control;
            propertyBinder = new PropertyBinder<TModel>(control);
        }

        protected abstract THIS thisInstance();

        public THIS IsVisible(Expression<Func<TModel, object>> expression)
        {
            this.visibilityExpression = expression;
            BindVisibility(expression, null);
            return thisInstance();
        }

        protected virtual void BindVisibility(Expression<Func<TModel, object>> expression, IFsCommand command)
        {
            propertyBinder.SetBindingWithConversion<BooleanToVisibilityConverter>(expression, UIElement.VisibilityProperty);
        }

        public BindModeExpression<THIS> BindMode
        {
            get
            {
                return new BindModeExpression<THIS>(thisInstance(),
                                                    b => propertyBinder.UpdateLastBinding(b));
            }
        }

        public THIS OnKeyDown<T>(Expression<Func<TModel, DelegateCommand<T>>> expression)
        {
            var command = PrepareCommand(expression, false);
            control.KeyDown += (sender, e) => command.Execute(null);
            return thisInstance();
        }

        public THIS OnKeyUp<T>(Expression<Func<TModel, DelegateCommand<T>>> expression)
        {
            var command = PrepareCommand(expression, false);
            control.KeyUp += (sender, e) => command.Execute(null);
            return thisInstance();
        }

        protected IFsCommand PrepareCommand<T>(Expression<Func<TModel, DelegateCommand<T>>> expression)
        {
            return PrepareCommand(expression, true);
        }

        protected IFsCommand PrepareCommand<T>(Expression<Func<TModel, DelegateCommand<T>>> expression, bool bindVisibility)
        {
            var fi = ((MemberExpression)expression.Body).Member as FieldInfo;
            if (fi != null)
            {
                var command = (IFsCommand)fi.GetValue(model);
                WireCommand(command);
                if (bindVisibility) BindVisibility(visibilityExpression, command);
                return command;
            }
            throw new ArgumentException("The expression cannot be used to bind an action/delegate command to an event.");
        }

        protected virtual void WireCommand(IFsCommand command)
        { }
    }
}